using System;
using System.Diagnostics;
using System.Globalization;
using System.Text;

using Pegasus.Diagnostics;

namespace Pegasus
{
	[Serializable]
	class DateTimeReoccurringMonthly : DateTimeReoccurring
	{
		// Local Instance Values
		private MonthlyMode m_mode = MonthlyMode.Mode1;
		private int m_dayOfMonth = 1;
		private int m_everyNumberOfMonths = 1;

		private WhichDay m_whichDay = WhichDay.First;
		private int m_dayOfWeek = (int) System.DayOfWeek.Sunday;

		/// <summary>
		/// Initializes a new instance of the <see cref="T:DateTimeReoccurringMonthly"/> class.
		/// </summary>
		public DateTimeReoccurringMonthly()
		{
		}

		/// <summary>
		/// Gets or sets the mode.
		/// </summary>
		/// <value>The mode.</value>
		public MonthlyMode Mode
		{
			get
			{
				return m_mode;
			}

			set
			{
				m_mode = value;
			}
		}

		/// <summary>
		/// Gets or sets the day of month.
		/// </summary>
		/// <value>The day of month.</value>
		public int DayOfMonth
		{
			get
			{
				return m_dayOfMonth;
			}

			set
			{
				ParamCode.Assert( value > 0 && value < 32, "DayOfMonth" );
				m_dayOfMonth = value;
			}
		}

		/// <summary>
		/// Gets or sets the every number of months (1 to 12).
		/// </summary>
		/// <value>The every number of months.</value>
		public int EveryNumberOfMonths
		{
			get
			{
				return m_everyNumberOfMonths;
			}

			set
			{
				ParamCode.Assert( value > 0 && value < 13, "EveryNumberOfMonths" );
				m_everyNumberOfMonths = value;
			}
		}

		/// <summary>
		/// Gets or sets the which day.
		/// </summary>
		/// <value>The which day.</value>
		public WhichDay WhichDayOfMonth
		{
			get
			{
				return m_whichDay;
			}

			set
			{
				m_whichDay = value;
			}
		}

		/// <summary>
		/// Gets or sets the day of week.  Matches the DayOfWeek enumeration with the addtion
		/// of a value of 7 as the day (any day)
		/// </summary>
		/// <value>The day of week.</value>
		public int DayOfWeek
		{
			get
			{
				return m_dayOfWeek;
			}

			set
			{
				m_dayOfWeek = value;
			}
		}

		/// <summary>
		/// Get the date and time of the occurrance of the pattern from the
		/// given date time.
		/// </summary>
		/// <param name="fromDate">The date to calculate the pattern from.</param>
		/// <returns>
		/// A <see cref="DateTime"/> of the next occurance.
		/// </returns>
		public override DateTime GetNextOccurrence( DateTime fromDate )
		{
			DateTime baseDate = StartDate;
			DateTime nextDate = fromDate.Date + RunTime.TimeOfDay;
			
			// If the fromDate is before the start date then use the start 
			// state as the staring point.
			if( fromDate < baseDate )
			{
				return GetNextOccurrence( baseDate );
			}

			if( m_mode == MonthlyMode.Mode1 )
			{
				nextDate = GetNextOccurrenceEveryDay( fromDate );
			}
			else
			{
				nextDate = GetNextOccurrenceSpecificDay( fromDate );
			}

			return nextDate;
		}

		/// <summary>
		/// Get the text description for the occurrence.
		/// </summary>
		/// <returns></returns>
		public override string GetDisplayText()
		{
			StringBuilder msg = new StringBuilder();

			if( m_mode == MonthlyMode.Mode1 )
			{
				if( m_everyNumberOfMonths == 1 )
				{
					msg.Append( "Every month on the " ).Append( m_dayOfMonth );
				}
				else
				{
					msg.Append( "Every " ).Append( m_everyNumberOfMonths )
						.Append( " months on the " ).Append( m_dayOfMonth );
				}
			}
			else
			{
				msg.Append( "The " ).Append( m_whichDay.ToString().ToLower() );

				if( m_dayOfWeek == 7 )
				{
					msg.Append( " day" );
				}
				else
				{
					msg.Append( (DayOfWeek) m_dayOfWeek );
				}

				if( m_everyNumberOfMonths == 1 )
				{
					msg.Append( " of every month" );
				}
				else
				{
					msg.Append( " of every " ).Append( m_everyNumberOfMonths )
						.Append( " months" );
				}
			}

			return msg.ToString();
		}

		/// <summary>
		/// Gets the next occurrence every day.
		/// </summary>
		/// <param name="fromDate">From date.</param>
		/// <returns></returns>
		private DateTime GetNextOccurrenceEveryDay( DateTime fromDate )
		{
			DateTime baseDate = StartDate;
			DateTime nextDate = fromDate.Date + RunTime.TimeOfDay;
			
			// Is this a month that we can run in
			int modMonths = GetModMonths( fromDate );
			if( modMonths == 0 )
			{
				int daysInMonth = GetDaysInMonth( fromDate );
				if( m_dayOfMonth > daysInMonth )
				{
					nextDate = fromDate.AddDays( -1 * ( ( m_dayOfMonth - daysInMonth ) - 1 ) ).Date + RunTime.TimeOfDay;
				}
				else
				{
					nextDate = fromDate.AddDays( ( ( daysInMonth - fromDate.Day ) - 1 ) ).Date + RunTime.TimeOfDay;
				}

				if( nextDate < fromDate )
				{
					// The date as passed so move to the next date.

					// Move to the first of this month, then add the number of
					// months, and then add the day of the month,
					// and finialy the run time
					nextDate = fromDate.Date.AddDays( -1 * ( fromDate.Day - 1 ) );
					nextDate = nextDate.AddMonths( m_everyNumberOfMonths );

					daysInMonth = GetDaysInMonth( nextDate );
					nextDate = nextDate.AddDays( Math.Min( m_dayOfMonth, daysInMonth ) - 1 );

					nextDate = nextDate + RunTime.TimeOfDay;
				}
			}
			else
			{
				// This is not a valid month so move to the next mod month.
				nextDate = fromDate.AddMonths( m_everyNumberOfMonths ).Date + RunTime.TimeOfDay;
			}

			return nextDate;
		}

		/// <summary>
		/// Gets the next occurrence specific day.
		/// </summary>
		/// <param name="fromDate">From date.</param>
		/// <returns></returns>
		private DateTime GetNextOccurrenceSpecificDay( DateTime fromDate )
		{
			DateTime baseDate = StartDate;
			DateTime nextDate = fromDate.Date + RunTime.TimeOfDay;

			// Is this a month that we can run in
			int modMonths = GetModMonths( fromDate );
			if( modMonths == 0 )
			{
				DateTime firstOfTheMonth = fromDate.AddDays( -1 * ( fromDate.Day - 1 ) ).Date;
				nextDate = GetDayOfWeek( m_whichDay, m_dayOfWeek, firstOfTheMonth );
			}

			if( modMonths != 0 || nextDate < fromDate )
			{
				nextDate = fromDate.AddMonths( m_everyNumberOfMonths ).Date;
				DateTime firstOfTheMonth = nextDate.AddDays( -1 * ( nextDate.Day - 1 ) );
				nextDate = GetDayOfWeek( m_whichDay, m_dayOfWeek, firstOfTheMonth );
			}

			return nextDate;
		}


		/// <summary>
		/// Gets the mod months.
		/// </summary>
		/// <param name="fromDate">From date.</param>
		/// <returns></returns>
		private int GetModMonths( DateTime fromDate )
		{
			int month = fromDate.Month + ( fromDate.Year * 12 );
			int startMonth = StartDate.Month + ( fromDate.Year * 12 );
			int diffMonths = startMonth - month;

			return diffMonths % m_everyNumberOfMonths;
		}

		/// <summary>
		/// Determine the mode to run in and which properties are used.
		/// </summary>
		public enum MonthlyMode
		{
			/// <summary>
			/// Uses the DayOfMonth and EveryNumberOfMonths to set the pattern to run on 
			/// the specific day of the month every X months.  If the day exceedes the last
			/// last day of the month then the pattern is executed on the last day of the 
			/// month.
			/// </summary>
			/// <example>
			///		if DayOfMonth = 31 and EveryNumberOfMonths = 1 then when the month is on
			///		Feb the patter will run on the 28 (or 29 in leap years).
			/// </example>
			Mode1,

			/// <summary>
			/// Uses the WhichDay, DayOfWeek, and EveryNumberOfMonths to set the pattern to run on.
			/// </summary>
			Mode2,
		}
	}
}
